#include <windows.h>
#include <commctrl.h>
#include <tchar.h>

#include "lib.h"
#include "lview.h"

ListView::ListView(HWND hLV)
{
	hList = hLV;
	ZeroMemory(colwid, NUMCOLS * sizeof(UINT));

	InitColumns();
}

ListView::~ListView()
{
}

void
ListView::InitColumns()
{
	TCHAR text[256];
	LV_COLUMN lvc; 
	RECT listRect;
 
	lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; 
	lvc.pszText = text; 
 
	lvc.iSubItem = 0; 
	lvc.cx = 100; 
	lvc.fmt = LVCFMT_LEFT; 
	_tcscpy(text, _T("Name"));
	ListView_InsertColumn(hList, 0, &lvc);
	colwid[0] = lvc.cx;

	lvc.iSubItem = 1; 
	lvc.cx = 50; 
	lvc.fmt = LVCFMT_CENTER; 
	_tcscpy(text, _T("Version"));
	ListView_InsertColumn(hList, 1, &lvc);
	colwid[1] = lvc.cx;

	lvc.iSubItem = 2; 
	lvc.cx = 50; 
	lvc.fmt = LVCFMT_LEFT; 
	_tcscpy(text, _T("Requires"));
	ListView_InsertColumn(hList, 2, &lvc);
	colwid[2] = lvc.cx;

	GetClientRect(hList, &listRect);
	lvc.iSubItem = 3; 
	lvc.cx = listRect.right - listRect.left - 50 - 100;
	lvc.fmt = LVCFMT_LEFT; 
	_tcscpy(text, _T("Zip file"));
	ListView_InsertColumn(hList, 3, &lvc);
	colwid[3] = lvc.cx;
}

void
ListView::selectItem(INT index)
{
	ListView_SetItemState(hList, index, LVIS_SELECTED | LVIS_FOCUSED, 0x000F);
}

INT
ListView::getSelectedItem()
{
	return ListView_GetNextItem(hList, -1, LVNI_SELECTED);
}

INT
ListView::addItem(TCHAR* name, TCHAR* version, TCHAR* requires, TCHAR* zipname)
{
	LV_ITEM lvi;
	INT index;

	lvi.mask = LVIF_TEXT | LVIF_STATE; 
	lvi.state = 0; 
	lvi.stateMask = 0; 
	lvi.pszText = name;

	lvi.iItem = ListView_GetItemCount(hList);
	lvi.iSubItem = 0;
	index = ListView_InsertItem(hList, &lvi);

	if (index != -1)
	{
		ListView_SetItemText(hList, lvi.iItem, 1, version);
		ListView_SetItemText(hList, lvi.iItem, 2, requires);
		ListView_SetItemText(hList, lvi.iItem, 3, zipname);
	}
	return index;
}

void
ListView::changeItem(INT index, TCHAR* name, TCHAR* version, TCHAR* zipname)
{
	ListView_SetItemText(hList, index, 0, name);
	ListView_SetItemText(hList, index, 1, version);
	ListView_SetItemText(hList, index, 2, zipname);
}

INT
ListView::deleteItem(INT index)
{
	return ListView_DeleteItem(hList, index);
}

void
ListView::updateHeaderWidth(HWND hHeader, INT item)
{
    HD_ITEM hdi;

    // Get the column width from the control
    hdi.mask = HDI_WIDTH;
    if (Header_GetItem(hHeader, item, &hdi))
        colwid[item] = hdi.cxy;

    // Force the ListView control to repaint
    InvalidateRect(hList, NULL, TRUE);
}

void
ListView::MeasureItem(MEASUREITEMSTRUCT* lpMeasureItem)
{
    TEXTMETRIC tm;
    HDC hdc;
	HFONT hFont;

	// Get the font the control is currently using
	hFont = (HFONT)(DWORD) SendMessage(hList, WM_GETFONT, 0, 0L);

	// Set the font of the DC to the same font the control is using
    hdc = GetDC(hList);
	SelectObject(hdc, hFont);

    // Get the height of the font used by the control
    if (!GetTextMetrics(hdc, &tm))
        return;

    // Add a little extra space between items
    lpMeasureItem->itemHeight = tm.tmHeight + 1;

#if 0
    // Make sure there is enough room for the images which are CY_SMICON high
    if (lpMeasureItem->itemHeight < (CY_SMICON + 1))
    	lpMeasureItem->itemHeight = CY_SMICON + 1;
#endif

	// Clean up
	ReleaseDC(hList, hdc);
}

void
ListView::DrawItem(LPDRAWITEMSTRUCT lpDrawItem)
{
    HIMAGELIST himl;
    LV_ITEM lvi;
	LV_COLUMN lvc;
    int cxImage = 0, cyImage = 0;
    UINT uFirstColWidth;
    RECT rcClip;
	UINT uiFlags = ILD_TRANSPARENT;
	TCHAR text[256];

    // Get the item image to be displayed
	//
    lvi.mask = LVIF_IMAGE | LVIF_STATE | LVIF_TEXT;
    lvi.iItem = lpDrawItem->itemID;
    lvi.iSubItem = 0;
	lvi.pszText = text;
	lvi.cchTextMax = 256;
    ListView_GetItem(lpDrawItem->hwndItem, &lvi);

    // Check to see if this item is selected
    if (lpDrawItem->itemState & ODS_SELECTED)
    {
        // Set the text background and foreground colors
        SetTextColor(lpDrawItem->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT));
        SetBkColor(lpDrawItem->hDC, GetSysColor(COLOR_HIGHLIGHT));

		// Also add the ILD_BLEND50 so the images come out selected
		uiFlags |= ILD_BLEND50;
    }
    else
    {
        // Set the text background and foreground colors to the standard window
        // colors
        SetTextColor(lpDrawItem->hDC, GetSysColor(COLOR_WINDOWTEXT));
        SetBkColor(lpDrawItem->hDC, GetSysColor(COLOR_WINDOW));
    }

    // Get the image list and draw the image
    himl = ListView_GetImageList(lpDrawItem->hwndItem, LVSIL_SMALL);
    if (himl)
    {
        ImageList_Draw(himl, lvi.iImage, lpDrawItem->hDC,
                       lpDrawItem->rcItem.left, lpDrawItem->rcItem.top,
                       uiFlags);

        // Find out how big the image we just drew was
        ImageList_GetIconSize(himl, &cxImage, &cyImage);
    }

    // Calculate the width of the first column after the image width.  If
    // There was no image, then cxImage will be zero.
    uFirstColWidth = colwid[0] - cxImage;

    // Set up the new clipping rect for the first column text and draw it
    rcClip.left = lpDrawItem->rcItem.left + cxImage;
    rcClip.right = lpDrawItem->rcItem.left + colwid[0];
    rcClip.top = lpDrawItem->rcItem.top;
    rcClip.bottom = lpDrawItem->rcItem.bottom;

	lvc.mask = LVCF_FMT;
	ListView_GetColumn(lpDrawItem->hwndItem, 0, &lvc);
    DrawItemColumn(lpDrawItem->hDC, text, &rcClip, lvc.fmt & 0x0f);

    rcClip.left = rcClip.right;
    rcClip.right = rcClip.left + colwid[1];
    lvi.iSubItem = 1;
    ListView_GetItem(lpDrawItem->hwndItem, &lvi);
	ListView_GetColumn(lpDrawItem->hwndItem, 1, &lvc);
    DrawItemColumn(lpDrawItem->hDC, text, &rcClip, lvc.fmt & 0x0f);

    rcClip.left = rcClip.right;
    rcClip.right = rcClip.left + colwid[2];
    lvi.iSubItem = 2;
    ListView_GetItem(lpDrawItem->hwndItem, &lvi);
	ListView_GetColumn(lpDrawItem->hwndItem, 2, &lvc);
    DrawItemColumn(lpDrawItem->hDC, text, &rcClip, lvc.fmt & 0x0f);

	 rcClip.left = rcClip.right;
    rcClip.right = rcClip.left + colwid[3];
    lvi.iSubItem = 3;
    ListView_GetItem(lpDrawItem->hwndItem, &lvi);
	ListView_GetColumn(lpDrawItem->hwndItem, 3, &lvc);
    DrawItemColumn(lpDrawItem->hDC, text, &rcClip, lvc.fmt & 0x0f);

    // If we changed the colors for the selected item, undo it
    if (lpDrawItem->itemState & ODS_SELECTED)
    {
        // Set the text background and foreground colors
        SetTextColor(lpDrawItem->hDC, GetSysColor(COLOR_WINDOWTEXT));
        SetBkColor(lpDrawItem->hDC, GetSysColor(COLOR_WINDOW));
    }

    // If the item is focused, now draw a focus rect around the entire row
    if (lpDrawItem->itemState & ODS_FOCUS)
    {
        // Adjust the left edge to exclude the image
        rcClip = lpDrawItem->rcItem;
        rcClip.left += cxImage;

        // Draw the focus rect
        DrawFocusRect(lpDrawItem->hDC, &rcClip);
    }
}

void
ListView::DrawItemColumn(HDC hdc, LPTSTR lpsz, LPRECT prcClip, int fmt)
{
    TCHAR szString[256];
	UINT align;

    // Check to see if the string fits in the clip rect.  If not, truncate
    // the string and add "...".
    lstrcpy(szString, lpsz);
    CalcStringEllipsis(hdc, szString, 256, prcClip->right - prcClip->left);

	switch (fmt)
	{
		default:
		case LVCFMT_LEFT:
			ExtTextOut(hdc, prcClip->left + 5, prcClip->top + 1,
				ETO_CLIPPED | ETO_OPAQUE,
				prcClip, szString, lstrlen(szString), NULL);
			break;

		case LVCFMT_CENTER:
			align = SetTextAlign(hdc, TA_CENTER | TA_TOP | TA_NOUPDATECP); 
			ExtTextOut(hdc,
				prcClip->left + ((prcClip->right - prcClip->left)>>1),
				prcClip->top + 1,
				ETO_CLIPPED | ETO_OPAQUE,
				prcClip, szString, lstrlen(szString), NULL);
			SetTextAlign(hdc, align);
			break;

		case LVCFMT_RIGHT:
			align = SetTextAlign(hdc, TA_RIGHT | TA_TOP | TA_NOUPDATECP); 
			ExtTextOut(hdc, prcClip->right - 5, prcClip->top + 1,
				ETO_CLIPPED | ETO_OPAQUE,
				prcClip, szString, lstrlen(szString), NULL);
			SetTextAlign(hdc, align);
			break;
	}
}

BOOL
ListView::CalcStringEllipsis(HDC hdc, LPTSTR lpszString,
	int cchMax, UINT uColWidth)
{
    const TCHAR szEllipsis[] = TEXT("...");
    SIZE   sizeString;
    SIZE   sizeEllipsis;
    int    cbString;
    LPTSTR lpszTemp;
    BOOL   fSuccess = FALSE;

    // Adjust the column width to take into account the edges
    uColWidth -= 10;

	// Allocate a string for us to work with.  This way we can mangle the
	// string and still preserve the return value
	lpszTemp = (LPTSTR) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cchMax);
	if (!lpszTemp)
		return FALSE;
	
	lstrcpy(lpszTemp, lpszString);

	// Get the width of the string in pixels
	cbString = lstrlen(lpszTemp);
	if (!GetTextExtentPoint32(hdc, lpszTemp, cbString, &sizeString))
	{
        HeapFree(GetProcessHeap(), 0, (LPVOID)lpszTemp);
		return FALSE;
	}

	// If the width of the string is greater than the column width shave
	// the string and add the ellipsis
	if ((ULONG)sizeString.cx > uColWidth)
	{
		if (!GetTextExtentPoint32(hdc, szEllipsis, lstrlen(szEllipsis),
								   &sizeEllipsis))
		{
			HeapFree(GetProcessHeap(), 0, (LPVOID)lpszTemp);
			return FALSE;
		}

		while (cbString > 0)
		{
			lpszTemp[--cbString] = 0;
			if (!GetTextExtentPoint32(hdc, lpszTemp, cbString, &sizeString))
			{
				HeapFree(GetProcessHeap(), 0, (LPVOID)lpszTemp);
				return FALSE;
			}

			if ((ULONG)(sizeString.cx + sizeEllipsis.cx) <= uColWidth)
			{
				// The string with the ellipsis finally fits, now make sure
				// there is enough room in the string for the ellipsis
				if (cchMax >= (cbString + lstrlen(szEllipsis)))
				{
					// Concatenate the two strings and break out of the loop
					lstrcat(lpszTemp, szEllipsis);
					lstrcpy(lpszString, lpszTemp);
					fSuccess = TRUE;
					break;
				}
			}
		}
	}
	else
	{
		// No need to do anything, everything fits great.
		fSuccess = TRUE;
	}

	// Free the memory
	HeapFree(GetProcessHeap(), 0, (LPVOID)lpszTemp);
	return fSuccess;
}
